﻿#define UseToolPartForDataSourceID

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.IO;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using System.Xml;
using System.Text;

using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;

using Behemoth.SharePoint;
using Behemoth.SharePoint.Logging;
using Behemoth.SharePoint.WebParts.ToolParts;

namespace Behemoth.SharePoint.WebParts {

  [Guid("da43ec37-57f8-4a47-82e3-4b9e685e54be")]
  [XmlRoot(Namespace = "Behemoth.SharePoint.WebParts")]
  public class FlexDataForm : DataFormWebPart {

    public FlexDataForm() : base() {
      this.ExportMode = WebPartExportMode.All;
    }

    #region Example Base Properties (Typically set by SP Designer)
    /*
			SuppressWebPartChrome="False" 
    WebDescription=""
    PartImageSmall="" 
    DataSourceID="" 
    MissingAssembly="Cannot import this Web Part."
    FrameType="Default"
    ConnectionID="00000000-0000-0000-0000-000000000000" 
    DetailLink=""
    ExportControlledProperties="True"
    IsVisible="True"
    AllowRemove="True"
    AllowEdit="True"
    ID="g_e64b76eb_0543_4fdb_939b_a67b70bc53c1"
    Dir="Default" 
    FrameState="Normal" 
    ViewContentTypeId="" 
    AllowConnect="True" 
    PageSize="-1" 
    AllowMinimize="True" 
    IsIncludedFilter="" 
    ShowWithSampleData="False" 
    HelpMode="Modeless" 
    ExportMode="All" 
    ViewFlag="0" 
    Title="FWR Reports" 
    HelpLink="" 
    AllowHide="True" 
    AllowZoneChange="True" 
    PartOrder="1" 
    UseSQLDataSourcePaging="True" 
    PartImageLarge="" 
    IsIncluded="True" 
    NoDefaultStyle="TRUE" 
    __MarkupType="vsattributemarkup" 
    __WebPartId="{C06DE289-50E8-4C91-AADB-B821BE7C9EAE}" 
    __AllowXSLTEditing="true" 
    WebPart="true" 
    Height="" 
    Width=""
    >
     */
    #endregion

    #region Internal Proeprties

    protected const string TwelveHive = @"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\";
    protected static string FeaturePath = TwelveHive + @"TEMPLATE\FEATURES\FlexWebParts\";
    protected static string xslPath = TwelveHive + @"TEMPLATE\XML\FlexDataForm\";

    bool _addDataSourcesToCollection = true;
    protected ErrorBag _errors = new ErrorBag();
    protected ULSTraceLogging _log = new ULSTraceLogging();

    protected SPList _targetList;

#if UseToolPartForDataSourceID
    protected override bool ShouldToolPartShowDataSourceID {
      get {
        return true;
      }
    }
#endif
 
    public bool IsConfiguredWithRequiredProperties {
      get {
        if (string.IsNullOrEmpty(this.Xsl) || string.IsNullOrEmpty(this.ParameterBindings))
          return false;
        return true;
      }
    }

    #endregion
    #region Web Part Proeprties

#if !WSSOnly
    [WebBrowsable(false)]
#else
    [WebBrowsable(true)]
#endif
    [Personalizable(PersonalizationScope.Shared)]
    [Category("List Properties")]
    [WebDisplayName("Web Url")]
    [WebDescription("The URL of the sharepoint web in which desired data is located, or leave blank to use current web.")]
#if !WSSOnly
    [Browsable(false)]
#else
    [Browsable(true)]
#endif
    [WebPartStorage(Storage.Shared)]
    [SPWebCategoryName("List Properties")]
    [DisplayName("Web Url")]
    [Description("The URL of the sharepoint web in which desired data is located, or leave blank to use current web.")]
    public string WebUrl {
      get {
        return _webUrl;
      }
      set {
        _webUrl = value;
      }
    }
    private string _webUrl = string.Empty;

    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    [Category("List Properties")]
    [WebDisplayName("List Name")]
    [WebDescription("Title of list that will be used as the data source.")]
    [Browsable(true)]
    [WebPartStorage(Storage.Shared)]
    [SPWebCategoryName("List Properties")]
    [DisplayName("List Name")]
    [Description("Title of list that will be used as the data source.")]
    public string DataSourceListName {
      get {
        return _listName;
      }
      set {
        _listName = value;
      }
    }
    private string _listName = string.Empty;

    [DefaultValue("")]
    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    [Category("Data Form Properties")]
    [WebDisplayName("Xsl File Name")]
    [WebDescription("If set, this is the name of an Xsl stylesheet stored in the 12 Hive in TEMPLATE/XML.")]
    [Browsable(true)]
    [WebPartStorage(Storage.Shared)]
    [SPWebCategoryName("Data Form Properties")]
    [DisplayName("Xsl File Name")]
    [Description("If set, this is the name of an Xsl stylesheet stored in the 12 Hive in TEMPLATE/XML.")]
    public string XslFileName {
      get {
        return _xslFileName;
      }
      set {
        _xslFileName = value;
      }
    }
    private string _xslFileName = string.Empty;

    [DefaultValue("")]
    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    [Category("Data Form Properties")]
    [WebDisplayName("Parameter Bindings Xml File Name")]
    [WebDescription("If set, this is the name of an xml file stored in the 12 Hive in TEMPLATE/XML that contains the ParameterBindings.")]
    [Browsable(true)]
    [WebPartStorage(Storage.Shared)]
    [SPWebCategoryName("Data Form Properties")]
    [DisplayName("Parameter Bindings Xml File Name")]
    [Description("If set, this is the name of an xml file stored in the 12 Hive in TEMPLATE/XML that contains the ParameterBindings.")]
    public string ParameterBindingsFileName {
      get {
        return _parameterBindingsFileName;
      }
      set {
        _parameterBindingsFileName = value;
      }
    }
    private string _parameterBindingsFileName = string.Empty;

    [DefaultValue(defaultQuery)]
    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    [Category("List Properties")]
    [WebDisplayName("List CAML Query")]
    [WebDescription("CAML Query tag to retreive the list from data source, can also be used for sorting and grouping.")]
    [Browsable(true)]
    [WebPartStorage(Storage.Shared)]
    [SPWebCategoryName("List Properties")]
    [DisplayName("List CAML Query")]
    [Description("CAML Query tag to retreive the list from data source, can also be used for sorting and grouping.")]
    public string ListCamlQuery {
      get {
        return _listCamlQuery;
      }
      set {
        _listCamlQuery = value;
      }
    }
    private string _listCamlQuery = defaultQuery;
    private const string defaultQuery =
      "<Query><OrderBy><FieldRef Name='Title' Ascending='TRUE'/><FieldRef Name='Created' Ascending='TRUE'/></OrderBy></Query>";

    [DefaultValue(2000)]
    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    [Category("List Properties")]
    [WebDisplayName("Maximum Rows")]
    [WebDescription("The largest number of rows to return from the data source. Numbers 0 or smaller imply no limit.")]
    [Browsable(true)]
    [WebPartStorage(Storage.Shared)]
    [SPWebCategoryName("List Properties")]
    [DisplayName("Maximum Rows")]
    [Description("The largest number of rows to return from the data source. Numbers 0 or smaller imply no limit.")]
    public int MaximumRows {
      get {
        return _maximumRows;
      }
      set {
        _maximumRows = value;
      }
    }
    private int _maximumRows = 2000;

    /// <summary>
    /// This text is loaded into the DataSources collection via a call to
    /// Page.TemplateControl.ParseControl(); It represents one or more
    /// data source controls, and should be prefixed by appropriate
    /// Register directives as would normally exist in an ASCX file.
    /// </summary>
    [DefaultValue("")]
    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    [Category("Data Form Properties")]
    [WebDisplayName("Data Sources")]
    [WebDescription("If provided and no other data sources are present, this is the text that will be used to create a DataSource object.")]
    [Browsable(true)]
    [WebPartStorage(Storage.Shared)]
    [SPWebCategoryName("Data Form Properties")]
    [DisplayName("Data Sources")]
    [Description("If provided and no other data sources are present, this is the text that will be used to create a DataSource object.")]
    public new string DataSourcesString {
      get { return base.DataSourcesString; }
      set { 
        if (string.Compare(base.DataSourcesString, value, false) != 0)
          base.DataSourcesString = value;
      }
    }

    [DefaultValue("")]
    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    [Category("Data Form Properties")]
    [WebDisplayName("Data Fields")]
    [WebDescription("Fields to be passed to the XSL transform.")]
    [Browsable(true)]
    [WebPartStorage(Storage.Shared)]
    [SPWebCategoryName("Data Form Properties")]
    [DisplayName("Data Fields")]
    [Description("Fields to be passed to the XSL transform.")]
    public new string DataFields {
      get { return base.DataFields; }
      set {
        if (string.Compare(base.DataFields, value, false) != 0)
          base.DataFields = value;
      }
    }

#if !UseToolPartForDataSourceID
    [DefaultValue("")]
    [WebBrowsable(true)]
    [Personalizable(PersonalizationScope.Shared)]
    [Category("Data Form Properties")]
    [WebDisplayName("Data Source ID")]
    [WebDescription("ID of the DataSources collection that will be bound to the Data Form.")]
    [Browsable(true)]
    [WebPartStorage(Storage.Shared)]
    [SPWebCategoryName("Data Form Properties")]
    [DisplayName("Data Source ID")]
    [Description("ID of the DataSources collection that will be bound to the Data Form.")]
    public new string DataSourceID {
      get { return base.DataSourceID; }
      set {
        if (string.Compare(base.DataSourceID, value, false) != 0)
          base.DataSourceID = value;
      }
    }
#endif

    #endregion

    #region Web Loading Code Pattern

    protected SPSite _currentSite;
    protected SPWeb _currentWeb;
    private bool _mustDisposeWeb = false;

    private void InitWeb() {
      if (string.IsNullOrEmpty(this.WebUrl)) {
        if (SPContext.Current == null || SPContext.Current.Web == null)
          throw new Exception("could not get the current web context.");
        _currentWeb = SPContext.Current.Web;
      } else {
        SPSite _currentSite = new SPSite(this.WebUrl);
        _currentWeb = _currentSite.OpenWeb(this.WebUrl);
        _mustDisposeWeb = true;
      }
    }
    private void CloseWebAndSite() {
      if (_mustDisposeWeb) {
        if (_currentWeb != null) {
          _currentWeb.Close();
          _currentWeb.Dispose();
          _currentWeb = null;
        }
        if (_currentSite != null) {
          _currentSite.Close();
          _currentSite.Dispose();
          _currentSite = null;
        }
      }
    }

    #endregion

    private void InitList() {
      if (string.IsNullOrEmpty(this.DataSourceListName) || _currentWeb == null)
        return;
      // TODO try to use both title of list and internal name to get the correct one
      _targetList = _currentWeb.Lists[this.DataSourceListName];
      if (_targetList == null)
        throw new Exception(string.Format("Could not find list with name '{0}' in web '{1}'.", this.DataSourceListName, this._currentWeb.Url));
    }

    protected override void OnInit(EventArgs e) {
      base.OnInit(e);
      try {
        // attempt to get desired resources from the filesystem if they are there
        GetParameterBindingsFromFileSystem();
        GetStylesheetFromFileSystem();
        // set up our current context
        InitWeb();
        InitList();
        // generate the data source
        if (_targetList != null) {
          this.ParameterBindings = ReplaceListIDInParameterBindings(this.ParameterBindings, _targetList);
          this.DataFields = BuildDataFieldsList(_targetList);
          // TODO implement a templated control to load the data source
          DataSourceControl ds = BuildListDataSource(_targetList);
          if (_addDataSourcesToCollection) {
            this.DataSourcesString = string.Empty;
            this.DataSources.Clear();
            this.DataSources.Add(ds);
            this.DataSourceID = ds.ID;
          } else {
            this.DataSource = ds;
          }
        }
        if (this.DataSource != null)
          this.DataBind();
      } catch (Exception ex) {
        HandleError("Exception occurred in OnInit()", ex, true);
      }
    }

    #region Data Source Builders

    public string ReplaceListIDInParameterBindings(string bindings, SPList list) {
      bindings = bindings.Replace("{LIST_ID}", list.ID.ToString());
      return bindings;
    }

    public string BuildDataFieldsList(SPList list) {
      //datafields = "@ID,ID;@ContentType,Content Type;@Created,Created;@Author,Created By;@Modified,Modified;@Editor,Modified By;@_CopySource,Copy Source;@CheckoutUser,Checked Out To;@FileLeafRef,Name (for use in forms);@_CheckinComment,Check In Comment;@CheckedOutTitle,Checked Out To;@CheckedOutUserId,ID of the User who has the item Checked Out;@FileDirRef,Path;@FSObjType,Item Type;@HTML_x0020_File_x0020_Type,HTML File Type;@File_x0020_Type,File Type;@IsCheckedoutToLocal,Is Checked out to local;@_SourceUrl,Source Url;@_HasCopyDestinations,Has Copy Destinations;@ContentTypeId,Content Type ID;@_ModerationStatus,Approval Status;@_UIVersion,UI Version;@Created_x0020_Date,Created;@FileRef,URL Path;@File_x0020_Size,File Size;@_UIVersionString,Version;@ParentVersionString,Source Version (Converted Document);@ParentLeafName,Source Name (Converted Document);@Title,Title;@_Comments,Report WebDescription;@_Author,Report Author;@ReportType,Report Type;@ReportCategory,Report SPWebCategoryName;@OrganizationalDomain,Organizational Domain;@EntityName,Entity Name;@EntityShortName,Entity Short Name;@ReportingDepartment,Source Department;@Frequency,Frequency;@FiscalYear,Fiscal Year;@ReportStartDate,Report Start Date;@ReportEndDate,Report End Date;@ReportStartMonth,First Month (Number);@ReportStartDay,First Day;@ReportEndMonth,Last Month (Number);@ReportEndDay,Last Day;@TemplateUrl,Template Link;";
      StringBuilder sb = new StringBuilder();
      foreach (SPField field in list.Fields) {
        sb.Append("@");
        sb.Append(field.InternalName);
        sb.Append(",");
        sb.Append(field.Title);
        sb.Append(";");
      }
      return sb.ToString();
    }

    /// <summary>
    /// Programmaticaly creates a single list data source based on the
    /// example file in TEMPALTES/XML/FlexDataForm/SampleDataSource.xml
    /// </summary>
    /// <param name="list"></param>
    /// <returns></returns>
    private SPDataSource BuildListDataSource(SPList list) {
      SPDataSource ds = new SPDataSource();
      ds.DataSourceMode = SPDataSourceMode.List;
      ds.SelectCommand = "<View>" + this.ListCamlQuery + "</View>";
      ds.UseInternalName = true;
      ds.ID = "dsFlexDataForm";
      ds.List = list;

      Parameter listIdParam = new Parameter("ListID");
      listIdParam.DefaultValue = list.ID.ToString("B").ToUpper();
      ds.SelectParameters.Add(listIdParam);
      ds.UpdateParameters.Add(listIdParam);
      ds.DeleteParameters.Add(listIdParam);
      ds.InsertParameters.Add(listIdParam);

      Parameter maximumRowsParam = new Parameter("MaximumRows");
      maximumRowsParam.DefaultValue = this.MaximumRows.ToString();
      if (this.MaximumRows > 0)
        ds.SelectParameters.Add(maximumRowsParam);

      QueryStringParameter rootFolderParam = new QueryStringParameter("RootFolder", "RootFolder");
      ds.SelectParameters.Add(rootFolderParam);

      return ds;
    }

    #endregion

    #region Read Some Properties From Filesystem

    private void GetParameterBindingsFromFileSystem() {
      if (string.IsNullOrEmpty(this.ParameterBindingsFileName))
        return;

      string filePath = xslPath + this.ParameterBindingsFileName;
      if (!File.Exists(filePath))
        throw new Exception(string.Format("The file specified by {0} at path '{1}' does not exist.", "ParameterBindingsFileName", filePath));

      XmlDocument paramXml = new XmlDocument();
      paramXml.Load(filePath);
      if (paramXml == null)
        throw new Exception(string.Format("Could not retrieve parameter bindings from the file specified by {0} at path '{1}'.", "ParameterBindingsFileName", filePath));
      XmlNode paramBindings = paramXml.SelectSingleNode("//ParameterBindings");
      if (paramBindings == null)
        throw new Exception(string.Format("ParameterBindings root element was not found in the file specified by {0} at path '{1}'.", "ParameterBindingsFileName", filePath));
      string paramText = paramBindings.InnerXml;
      if (string.IsNullOrEmpty(paramText))
        throw new Exception(string.Format("The file specified by {0} at path '{1}' contains no data.", "ParameterBindingsFileName", filePath));

      this.ParameterBindings = paramText;
    }

    private void GetStylesheetFromFileSystem() {
      if (string.IsNullOrEmpty(XslFileName))
        return;
      string filePath = xslPath + this.XslFileName;
      if (!File.Exists(filePath))
        throw new Exception(string.Format("The file specified by {0} at path '{1}' does not exist.", "XslFileName", filePath));

      string styleSheet;
      StreamReader sr = File.OpenText(filePath);
      styleSheet = sr.ReadToEnd();
      if (string.IsNullOrEmpty(styleSheet))
        throw new Exception(string.Format("The file specified by XslFileName at path '{0}' contains no data.", filePath));

      // TODO validate the XSL and return an error that will actually help in troubleshooting it.
      //use styleSheet and this.SampleData to validate the XSL;

      this.Xsl = styleSheet;
      /*
        XmlDocument xml = GetStylesheet();
        if (xml == null)
          throw new Exception("Could not retrieve stylesheet.");
        styleSheet = xml.InnerXml;
        styleSheet = styleSheet.Replace("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>", string.Empty);
        if (string.IsNullOrEmpty(styleSheet))
          throw new Exception("No XSL text in Xml document.");
       */
    }

    #endregion

    #region Controls and Rendering

    protected override void CreateChildControls() {
      try {
        base.CreateChildControls();
      } catch (Exception ex) {
        HandleError("Exception occurred in CreateChildControls()", ex, true);
      }
    }

    protected override void Render(HtmlTextWriter writer) {
      try {
        // output any errors, warnings, or info messages that may have been added.
        _errors.Render(writer);
        // if there was an error don't attempt to render the web part
        if (this._errors.HasErrors)
          return;
        else {
          if (!this.IsConfiguredWithRequiredProperties)
            WebPartUtilities.RenderToolPaneEditLink(writer, string.Empty, this);
          else
            base.Render(writer);
        }
      } catch (Exception ex) {
        _log.Write(ex);
        writer.Write(string.Format("<p>Unexpected Exception while rendering web part. </p><div>Exception message: {0}</div><div> Stack Trace: {1}</div>", ex.Message, ex.StackTrace));
      } finally {
        // try very, very hard to clean up gracefully and prevent memory leaks
        CloseWebAndSite();
      }
    }

    private void HandleError(string msg, Exception ex, bool isError) {
      if (isError || ex != null) {
        _errors.AddError(msg);
        _errors.AddError(ex);
      } else {
        _errors.AddWarning(msg);
      }
      _log.Write(msg, isError ? TraceSeverity.Unexpected : TraceSeverity.WarningEvent);
      if (ex != null)
        _log.Write(ex);
    }

    #endregion

#if !WSSOnly
    public override ToolPart[] GetToolParts() {
      List<ToolPart> parts = new List<ToolPart>();
      SiteAndListPickerToolPart picker = new SiteAndListPickerToolPart();

      parts.AddRange(base.GetToolParts());

      picker.Items.Add(new SiteAndListPickerItem("Site to query", "WebUrl", SiteAndListPickerType.Site));
      picker.Title = "Select data source";
      parts.Add(picker);

      //if desired, you can use the picker to also select the list
      return parts.ToArray();
    }
#endif

  } // class

} // namespace
